package main

//权限模块，记录到Redis缓存中
//该模块使用Linux的cron跑定时任务，1分钟执行一次

import (
	"errors"
	"fmt"
	"github.com/go-redis/redis"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"log"
	"strconv"
)

//方法表
type Method struct {
	ID int32
	Name string
	ParentId int32
}

//角色方法表
type RoleMethod struct {
	RoleId int32 `gorm:"column:role_id;primaryKey"`
	MethodId int32
}

const(
	KEY_RBAC_REFRESH = "rbac_handler_refresh"		//强制刷新rbac的标识key
	KEY_RBAC_REFRESH_TIMES = "rbac_handler_times" 	//累计cron执行检测次数key

	//Redis的命名格式：
	//--角色对应权限用set集合存储
	//----格式key rbac_role_角色id
	//----格式val []string{方法id}

	//--服务方法用hash类型存储
	//----格式 key:rbac_method
	//----    field:rbac_method_方法名
	//----    val:方法id

	KEY_RBAC_ROLE_PREFIX = "rbac_role_"				//角色对应权限的key前缀
	KEY_RBAC_METHOD = "rbac_method"					//服务方法hash结构的key值
	KEY_RBAC_METHOD_PREFIX = "rbac_method_"			//服务方法hash结构的field前缀


	DB_USER = "xdrg"
	DB_PWD = "xdrg123456"
	DB_ADDR = "127.0.0.1"
	DB_PORT = 3306
	DB_NAME = "emicro"

	REDIS_ADDR = "127.0.0.1"
	REDIS_PORT = 6379
)

func main(){
	addr := fmt.Sprintf("%v:%d", REDIS_ADDR, REDIS_PORT)
	redis := redis.NewClient(&redis.Options{Addr:addr})
	defer redis.Close()
	_, err := redis.Ping().Result()

	if err != nil {
		log.Println("err :", err)
		return
	}

	//刷新标识
	if redis.Get(KEY_RBAC_REFRESH).Val() == "1" {
		if refresh_rbac_handler(redis) == nil {
			redis.Del(KEY_RBAC_REFRESH)
		}
		return
	}

	if redis.HLen(KEY_RBAC_METHOD).Val() <= 0 {
		refresh_rbac_methods(redis)
	}

	//只判断角色1
	if redis.SCard(KEY_RBAC_ROLE_PREFIX + "1").Val() <= 0{
		refresh_rbac_role_methods(redis)
	}

	//30分钟检测一次新增角色和权限
	if redis.Get(KEY_RBAC_REFRESH_TIMES).Val() == "30"{
		if refresh_rbac_handler(redis)!= nil{
			return
		}
		redis.Set(KEY_RBAC_REFRESH_TIMES, 1, 0)
	}else{
		redis.Incr(KEY_RBAC_REFRESH_TIMES)
	}
}

func new_gorm() (*gorm.DB, error){
	dsn := fmt.Sprintf("%v:%v@tcp(%v:%d)/%v?charset=utf8mb4&parseTime=True&loc=Local",
		DB_USER,DB_PWD,DB_ADDR,DB_PORT,DB_NAME)

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,  //禁用表名复数
		},
	})

	if err!=nil{
		log.Println("Init Gorm Err:", err)
		return nil, err
	}
	return db, nil
}

func refresh_rbac_handler(redis *redis.Client) error {
	db, err := new_gorm()
	if err != nil{
		return err
	}

	err = refresh_rbac_methods_with_db(redis, db)
	if err != nil{
		log.Println("refresh_rbac_methods_with_db Err:", err)
		return err
	}
	err = refresh_rbac_role_methods_with_db(redis, db)
	if err != nil{
		log.Println("refresh_rbac_role_methods_with_db Err:", err)
		return err
	}
	var methods []Method
	db.Find(&methods)

	var role_methods []RoleMethod
	db.Find(&role_methods)

	return nil
}

func refresh_rbac_methods(redis *redis.Client) error {
	db, err := new_gorm()
	if err != nil{
		return err
	}
	return refresh_rbac_methods_with_db(redis, db)
}

func refresh_rbac_methods_with_db(redis *redis.Client, db *gorm.DB) error {
	var methods []Method
	db.Find(&methods)
	if len(methods) <= 0 {
		return errors.New("methods is empty")
	}
	maps := make(map[string]interface{})
	for _, method := range methods{
		maps[KEY_RBAC_METHOD_PREFIX + method.Name] = method.ID
	}
	_, err := redis.HMSet(KEY_RBAC_METHOD, maps).Result()
	return err
}

func refresh_rbac_role_methods(redis *redis.Client) error {
	db, err := new_gorm()
	if err != nil{
		return err
	}
	return refresh_rbac_role_methods_with_db(redis, db)
}

func refresh_rbac_role_methods_with_db(redis *redis.Client, db *gorm.DB) error {
	var role_methods []RoleMethod
	db.Find(&role_methods)
	if len(role_methods) <= 0 {
		return errors.New("role_methods is empty")
	}
	maps := make(map[int32][]string)
	for _, method := range role_methods{
		if methods, ok := maps[method.RoleId]; ok{
			maps[method.RoleId] = append(methods, strconv.Itoa(int(method.MethodId)))
		}else{
			maps[method.RoleId] = []string{strconv.Itoa(int(method.MethodId))}
		}
	}
	for role_id, methods := range maps {
		_, err := redis.SAdd(KEY_RBAC_ROLE_PREFIX+strconv.Itoa(int(role_id)), methods).Result()
		if err != nil {
			return errors.New("rbac_role_ Err:"+err.Error())
		}
	}

	return nil
}